#运行请使用命令：streamlit run app.py
import io
import os
# 在文件开头导入 os 后添加
os.environ['VTK_USE_OSMESA'] = '1'  # 强制使用 OSMesa 软件渲染
os.environ['PYVISTA_USE_IPYGANY'] = 'false'  # 禁用硬件加速

import streamlit as st
import numpy as np
import matplotlib.pyplot as plt
import pyvista as pv
from stpyvista import stpyvista
import nibabel as nib
import torch
from torch.utils.data import Dataset, DataLoader
from preprocess import preprocess_volume, crop_center
from train import UNetPlusPlus as UNet, ModelConfig, dice_coeff
import torch.nn.functional as F
# API调用相关导入
import json
from typing import Any, Dict
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.hunyuan.v20230901 import hunyuan_client, models
from datetime import datetime

# === 自定义评估函数 ===
def calculate_metrics(pred_volume: np.ndarray, true_volume: np.ndarray) -> Dict[str, float]:
    """计算分割结果的Dice系数和IoU指标
    
    Args:
        pred_volume: 预测分割结果 (3D数组)
        true_volume: 真实标签 (3D数组)
        
    Returns:
        Dict: 包含评估指标的字典
    """
    # 确保输入为整数类型
    pred = pred_volume.astype(np.int32)
    true = true_volume.astype(np.int32)
    
    # 计算肝脏指标
    liver_pred = (pred == 1)
    liver_true = (true == 1)
    liver_intersection = np.sum(liver_pred & liver_true)
    liver_union = np.sum(liver_pred | liver_true)
    liver_dice = (2.0 * liver_intersection) / (np.sum(liver_pred) + np.sum(liver_true) + 1e-8)
    liver_iou = liver_intersection / (liver_union + 1e-8)
    
    # 计算肿瘤指标
    tumor_pred = (pred == 2)
    tumor_true = (true == 2)
    tumor_intersection = np.sum(tumor_pred & tumor_true)
    tumor_union = np.sum(tumor_pred | tumor_true)
    tumor_dice = (2.0 * tumor_intersection) / (np.sum(tumor_pred) + np.sum(tumor_true) + 1e-8)
    tumor_iou = tumor_intersection / (tumor_union + 1e-8)
    
    return {
        'liver_dice': liver_dice,
        'tumor_dice': tumor_dice,
        'liver_iou': liver_iou,
        'tumor_iou': tumor_iou
    }

# === 重写评估函数 ===
def evaluate_segmentation(pred_volume: np.ndarray, true_volume: np.ndarray) -> Dict[str, Any]:
    """评估分割结果
    
    Args:
        pred_volume: 预测分割结果 (3D数组)
        true_volume: 真实标签 (3D数组)
        
    Returns:
        Dict: 评估结果字典
    """
    try:
        # 验证输入尺寸
        if pred_volume.shape != true_volume.shape:
            raise ValueError(f"预测结果({pred_volume.shape})与真实标签({true_volume.shape})尺寸不匹配")
            
        # 更严格的类别值范围验证
        pred_volume = np.clip(pred_volume.astype(np.int32), 0, 2)  # 强制限制在0-2范围内并确保整数类型
        true_volume = np.clip(true_volume.astype(np.int32), 0, 2)  # 强制限制在0-2范围内并确保整数类型
        
        # 检查是否有无效值
        if np.any(pred_volume > 2) or np.any(pred_volume < 0):
            st.warning("预测结果包含无效值(0-2之外)，已自动修正")
            
        if np.any(true_volume > 2) or np.any(true_volume < 0):
            st.warning("真实标签包含无效值(0-2之外)，已自动修正")

        # === 直接调用自定义指标计算函数 ===
        metrics = calculate_metrics(pred_volume, true_volume)
        
        # 返回评估结果
        return {
            'avg_liver_dice': metrics['liver_dice'],
            'avg_tumor_dice': metrics['tumor_dice'],
            'avg_liver_iou': metrics['liver_iou'],
            'avg_tumor_iou': metrics['tumor_iou']
        }
    except Exception as e:
        import traceback
        error_detail = traceback.format_exc()
        st.error(f"评估失败: {str(e)}\n错误详情:\n{error_detail}")
        return None

# 导入PDF生成所需模块
from fpdf import FPDF
import tempfile
from datetime import datetime

# === PDF生成函数 - 解决中文乱码和弃用警告 ===
def generate_pdf_report(report_content, filename="liver_report.pdf"):
    """生成PDF格式的诊断报告"""
    try:
        pdf = FPDF()
        pdf.add_page()
        
        # 改进字体加载逻辑并添加错误提示
        font_loaded = False
        font_options = [
            # 尝试系统可能存在的常见中文字体
            ('SimSun', '', 'C:/Windows/Fonts/simsun.ttc'),  # Windows常见路径
            ('SimHei', '', 'C:/Windows/Fonts/simhei.ttf'),
            ('Microsoft YaHei', '', 'C:/Windows/Fonts/msyh.ttc'),
            # 尝试默认字体
            ('Arial', '', 'Arial.ttf'),
            ('Helvetica', '', 'Helvetica.ttf')
        ]
        
        for font_name, style, font_file in font_options:
            try:
                pdf.add_font(font_name, style, font_file)
                pdf.set_font(font_name, size=12)
                font_loaded = True
                st.success(f"成功加载字体: {font_name}")
                break
            except RuntimeError as e:
                st.warning(f"尝试加载字体 {font_file} 失败: {str(e)}")
                continue
        
        if not font_loaded:
            st.error("""
            无法加载任何中文字体，PDF报告中的中文将无法正确显示。
            解决方法:
            1. 确保系统中安装了以下字体之一:
               - 宋体 (simsun.ttc)
               - 黑体 (simhei.ttf)
               - 微软雅黑 (msyh.ttc)
               - 思源黑体 (NotoSansCJK-Regular.ttc)
            2. 或者将字体文件放在程序目录下的fonts文件夹中
            """)
            # 使用内置字体作为最后回退
            pdf.set_font("Arial", size=12)
        
        # 添加标题
        pdf.cell(200, 10, text="肝脏与肝肿瘤影像分析报告", 
                new_x="LMARGIN", new_y="NEXT", align='C')
        
        # 设置正文字体大小
        pdf.set_font(size=10)
        
        # 添加报告内容
        pdf.multi_cell(0, 8, text=report_content)
        
        # 保存到临时文件
        temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf")
        pdf.output(temp_file.name)
        return temp_file.name
    except Exception as e:
        st.error(f"生成PDF失败: {str(e)}")
        return None

# === 优化VolumeDataset类 ===
class VolumeDataset(Dataset):
    def __init__(self, volume):
        self.volume = volume
        self.depth = volume.shape[2]
        
    def __len__(self):
        return self.depth - 2
    
    def __getitem__(self, idx):
        # 与preprocess.py完全一致的切片组合方式 [n, n+1, n+2]
        slice_curr = self.volume[:, :, idx]
        slice_next1 = self.volume[:, :, idx+1]
        slice_next2 = self.volume[:, :, idx+2]
        
        # 堆叠成三通道 (H, W, 3) -> 转换为 (3, H, W)
        input_data = np.stack([slice_curr, slice_next1, slice_next2], axis=0)
        return torch.tensor(input_data, dtype=torch.float32)

# 设置matplotlib中文字体
import matplotlib as mpl
mpl.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'KaiTi', 'FangSong', 'sans-serif']
mpl.rcParams['axes.unicode_minus'] = False  # 正确显示负号

# 预计算切片图像的函数
def precompute_slices(volume):
    """
    预计算所有切片图像数据
    """
    slices = {
        'coronal': [],   # 冠状面切片
        'sagittal': [],  # 矢状面切片
        'axial': []      # 横断面切片
    }
    
    # 预计算冠状面切片（统一旋转90度）
    for x_idx in range(volume.shape[2]):
        slice_data = np.rot90(volume[:, :, x_idx])  # 统一旋转90度
        slices['coronal'].append(slice_data)
    
    # 预计算矢状面切片（统一旋转90度）
    for y_idx in range(volume.shape[1]):
        slice_data = np.rot90(volume[:, y_idx, :])  # 统一旋转90度
        slices['sagittal'].append(slice_data)
    
    # 预计算横断面切片（统一旋转90度）
    for z_idx in range(volume.shape[0]):
        slice_data = np.rot90(volume[z_idx, :, :])  # 统一旋转90度
        slices['axial'].append(slice_data)
    
    return slices

# 统一模型加载函数
def init_or_load_model():
    """初始化或加载模型"""
    if 'model' not in st.session_state or st.session_state.model is None:
        try:
            # 尝试加载默认模型
            default_model_path = './training_output/best_model.pth'
            if os.path.exists(default_model_path):
                config = ModelConfig()
                model = UNet(
                    in_channels=config.IN_CHANNELS,
                    out_channels=config.OUT_CHANNELS
                ).to(st.session_state.device)
                model.load_state_dict(torch.load(default_model_path, map_location=st.session_state.device))
                model.eval()
                st.session_state.model = model
                st.session_state.last_loaded_model = 'default'
        except Exception as e:
            st.error(f"默认模型加载失败: {str(e)}")

# 在初始化后调用
init_or_load_model()

# 设置页面布局
st.set_page_config(
    page_title="肝脏与肝肿瘤影像分析系统",
    page_icon="🩺",
    layout="wide",
    initial_sidebar_state="expanded"
)

# 应用标题和说明
st.title("🩺 肝脏与肝肿瘤影像分析系统")
st.markdown("""
<div style="background-color:#f0f2f6;padding:15px;border-radius:10px;margin-bottom:20px;">
    <h3 style="color:#2c3e50;">医学影像分析与AI报告生成平台</h3>
    <p>上传CT影像，系统将自动进行肝脏与肝肿瘤分割，生成3D可视化结果，并提供AI辅助诊断报告。</p>
</div>
""", unsafe_allow_html=True)

# 初始化会话状态
if 'uploaded_file' not in st.session_state:
    st.session_state.uploaded_file = None
if 'volume_data' not in st.session_state:
    st.session_state.volume_data = None
if 'segmentation' not in st.session_state:
    st.session_state.segmentation = None
if 'report_generated' not in st.session_state:
    st.session_state.report_generated = False
# 状态变量
if 'current_x' not in st.session_state:
    st.session_state.current_x = 0
if 'current_y' not in st.session_state:
    st.session_state.current_y = 0
if 'current_z' not in st.session_state:
    st.session_state.current_z = 0
if 'model' not in st.session_state:
    st.session_state.model = None
if 'device' not in st.session_state:
    st.session_state.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# 真实标签状态变量
if 'uploaded_label' not in st.session_state:
    st.session_state.uploaded_label = None
if 'label_volume' not in st.session_state:
    st.session_state.label_volume = None

# 初始化3D可视化选项状态
if 'show_liver' not in st.session_state:
    st.session_state.show_liver = True
if 'show_tumor' not in st.session_state:
    st.session_state.show_tumor = True
if 'opacity' not in st.session_state:
    st.session_state.opacity = 0.7
if 'view' not in st.session_state:
    st.session_state.view = '3D视图'

# 初始化会话状态
if 'model_type' not in st.session_state:
    st.session_state.model_type = '预测模型'  # 添加模型类型状态变量，默认预测模型

# === 初始化临时控制状态变量 ===
if 'temp_model_type' not in st.session_state:
    st.session_state.temp_model_type = st.session_state.model_type
if 'temp_show_liver' not in st.session_state:
    st.session_state.temp_show_liver = st.session_state.show_liver
if 'temp_show_tumor' not in st.session_state:
    st.session_state.temp_show_tumor = st.session_state.show_tumor
if 'temp_opacity' not in st.session_state:
    st.session_state.temp_opacity = st.session_state.opacity
if 'temp_view' not in st.session_state:
    st.session_state.temp_view = st.session_state.view

# 加载训练好的模型
def load_model(model_file=None):
    config = ModelConfig()
    model = UNet(in_channels=3, out_channels=3).to(st.session_state.device)
    
    try:
        if model_file is not None:
            # 从文件对象加载模型
            model_bytes = model_file.getvalue()
            model.load_state_dict(torch.load(
                io.BytesIO(model_bytes), 
                map_location=st.session_state.device
            ))
        else:
            # 默认模型路径
            model_path = './training_output/best_model.pth'
            if os.path.exists(model_path):
                model.load_state_dict(torch.load(
                    model_path, 
                    map_location=st.session_state.device
                ))
            else:
                raise FileNotFoundError("未找到默认模型文件")
    except Exception as e:
        st.error(f"模型加载失败: {str(e)}")
        return None
    
    model.eval()
    return model

# 加载NIfTI文件
def load_nifti_file(file):
    # 直接获取文件字节内容
    file_bytes = file.getvalue()
    # 使用nibabel的from_bytes方法直接加载字节数据
    nifti_img = nib.Nifti1Image.from_bytes(file_bytes)
    volume = nifti_img.get_fdata()
    return volume.astype(np.float32)

# 使用批量预测替代逐切片预测 - 对齐evaluate.py逻辑
def predict_volume(model, volume, threshold, smoothness, progress_bar=None):  # progress_bar参数
    device = next(model.parameters()).device
    
    # 创建数据集和DataLoader
    dataset = VolumeDataset(volume)
    loader = DataLoader(
        dataset, 
        batch_size=8, 
        shuffle=False,
        num_workers=0,
        pin_memory=True
    )
    
    segmentation = np.zeros(volume.shape[:3], dtype=np.uint8)
    total_batches = len(loader)  # 计算总批次数
    
    model.eval()
    with torch.no_grad():
        # 初始化进度
        if progress_bar:
            progress_bar.progress(0.1)
        
        for batch_idx, batch_data in enumerate(loader):
            batch_data = batch_data.to(device)
            
            # 模型预测
            outputs = model(batch_data)
            
            # 使用阈值参数处理预测结果
            probs = F.softmax(outputs, dim=1)
            # 将布尔张量转换为整数类型后再执行argmax
            preds = (probs > threshold).long().argmax(dim=1).cpu().numpy()
            
            # 存储预测结果 - 只保存中间切片
            batch_size = batch_data.size(0)
            start_idx = batch_idx * loader.batch_size
            for i in range(batch_size):
                current_idx = start_idx + i
                if current_idx < len(dataset):
                    segmentation[:, :, current_idx] = preds[i]
            
            # 更新进度条
            if progress_bar:
                current_progress = 0.1 + 0.8 * ((batch_idx + 1) / total_batches)
                progress_bar.progress(current_progress)
            
            # 及时释放内存
            del outputs, preds
            torch.cuda.empty_cache()
    
    # 应用平滑度参数
    if smoothness > 0:
        from scipy.ndimage import median_filter
        segmentation = median_filter(segmentation, size=smoothness)
    
    # === 更严格的预测结果范围验证 ===
    segmentation = np.clip(segmentation, 0, 2)  # 强制限制在0-2范围内
    segmentation = segmentation.astype(np.uint8)  # 确保整数类型
    
    return segmentation

#生成诊断报告函数
def generate_diagnostic_report(volume, segmentation, eval_results=None):
    # 计算实际指标
    voxel_volume = 0.001  # 假设每个体素为1mm³，转换为cm³
    liver_mask = segmentation == 1
    tumor_mask = segmentation == 2
    
    liver_volume = np.sum(liver_mask) * voxel_volume
    tumor_volume = np.sum(tumor_mask) * voxel_volume
    
    # 识别肿瘤连通区域
    from scipy import ndimage
    labeled_tumors, num_tumors = ndimage.label(tumor_mask)
    
    # 计算最大肿瘤直径
    max_diameter = 0.0
    if num_tumors > 0:
        for i in range(1, num_tumors + 1):
            tumor_region = (labeled_tumors == i)
            slices = ndimage.find_objects(tumor_region)[0]
            size_z = slices[2].stop - slices[2].start
            size_y = slices[1].stop - slices[1].start
            size_x = slices[0].stop - slices[0].start
            tumor_diameter = max(size_x, size_y, size_z) * 0.1  # 0.1cm/体素
            if tumor_diameter > max_diameter:
                max_diameter = tumor_diameter
    
    # 返回关键指标
    return liver_volume, num_tumors, tumor_volume, max_diameter

# 可视化样本预测 (参考evaluate.py)
def visualize_sample_predictions(images, preds, num_samples=3):
    # 确保images和preds为4D数组 (batch, channel, H, W)
    if images.ndim == 3:
        images = images[:, np.newaxis, :, :]
    if preds.ndim == 2:
        preds = preds[np.newaxis, :, :]
    
    fig, axes = plt.subplots(num_samples, 3, figsize=(15, 5*num_samples))
    
    for i in range(min(num_samples, images.shape[0])):
        # 原始图像
        img_slice = np.squeeze(images[i, 0])
        axes[i, 0].imshow(img_slice, cmap='gray')
        axes[i, 0].set_title(f"样本 {i+1}: 原始CT")
        axes[i, 0].axis('off')
        
        # 预测结果
        pred_slice = np.squeeze(preds[i])
        axes[i, 1].imshow(pred_slice, cmap='jet', vmin=0, vmax=2)
        axes[i, 1].set_title("预测结果")
        axes[i, 1].axis('off')
        
        # 叠加显示
        axes[i, 2].imshow(img_slice, cmap='gray')
        tumor_mask = np.ma.masked_where(pred_slice != 2, pred_slice == 2)
        axes[i, 2].imshow(tumor_mask, cmap='Reds', alpha=0.7)
        axes[i, 2].set_title("肿瘤区域叠加")
        axes[i, 2].axis('off')
    
    plt.tight_layout()
    return fig

# 腾讯混元API调用函数
def call_hunyuan_api(secret_id, secret_key, prompt, model="hunyuan-standard"):
    try:
        cred = credential.Credential(secret_id, secret_key)
        httpProfile = HttpProfile()
        httpProfile.endpoint = "hunyuan.tencentcloudapi.com"
        clientProfile = ClientProfile()
        clientProfile.httpProfile = httpProfile
        client = hunyuan_client.HunyuanClient(cred, "ap-guangzhou", clientProfile)
        
        req = models.ChatCompletionsRequest()
        params = {
            "Model": model,
            "Messages": [{"Role": "user", "Content": prompt}],
            "Temperature": 0.5,
            "TopP": 0.8,
            "EnableEnhancement": True
        }
        req.from_json_string(json.dumps(params))
        
        resp = client.ChatCompletions(req)
        if resp.Choices:
            return resp.Choices[0].Message.Content
        return "API调用成功，但未返回有效内容"
    
    except TencentCloudSDKException as err:
        return f"API调用失败: {str(err)}"
    except Exception as e:
        return f"发生未知错误: {str(e)}"

#加载真实标签文件
def load_label_file(file):
    file_bytes = file.getvalue()
    nifti_img = nib.Nifti1Image.from_bytes(file_bytes)
    volume = nifti_img.get_fdata()
    
    # === 更严格的标签验证 ===
    volume = np.clip(volume, 0, 2)  # 强制限制在0-2范围内
    volume = volume.astype(np.uint8)  # 确保整数类型
    
    # 检查是否有无效值
    unique_values = np.unique(volume)
    if len(unique_values) > 3 or any(v not in {0,1,2} for v in unique_values):
        st.warning("标签文件包含无效值(0-2之外)，已自动修正")
        volume = np.where((volume < 0) | (volume > 2), 0, volume)  # 将无效值设为0
    
    return volume

# 侧边栏 - 数据输入
with st.sidebar:
    st.header("📁 数据输入")
    
    # 上传文件区域 - max_upload_size 参数
    uploaded_file = st.file_uploader("上传CT影像 (NIfTI格式)", type=["nii", "nii.gz"], key="ct_upload")
    
    # 真实标签上传区域 - max_upload_size 参数
    uploaded_label = st.file_uploader("导入真实标签 (NIfTI格式)", type=["nii", "nii.gz"], key="label_upload")
    if uploaded_label:
        st.session_state.uploaded_label = uploaded_label
    
    if uploaded_file:
        st.session_state.uploaded_file = uploaded_file
        # 立即加载数据
        try:
            st.session_state.volume_data = load_nifti_file(uploaded_file)
            
            # 预计算所有切片图像
            with st.spinner("预加载切片图像中..."):
                st.session_state.precomputed_slices = precompute_slices(st.session_state.volume_data)
            
            # 设置初始切片位置为中间
            st.session_state.current_x = st.session_state.volume_data.shape[2] // 2
            st.session_state.current_y = st.session_state.volume_data.shape[1] // 2
            st.session_state.current_z = st.session_state.volume_data.shape[0] // 2
            
            st.success("文件加载成功！")
        except Exception as e:
            st.error(f"文件加载失败: {str(e)}")
    
    st.divider()
    
    # 模型设置
    st.header("⚙️ 模型设置")
    
    # === 简化模型加载逻辑 ===
    st.subheader("模型选择")
    model_file = st.file_uploader(
        "选择模型文件 (.pth)", 
        type=["pth"], 
        key="model_upload",
        help="从操作系统资源管理器中选择训练好的模型文件"
    )
    
    if model_file is not None:
        with st.spinner("正在加载模型..."):
            try:
                # 直接更新session_state中的模型
                st.session_state.model = load_model(model_file)
                st.session_state.last_loaded_model = model_file.name
                st.success(f"模型加载成功! 文件名: {model_file.name}")
            except Exception as e:
                st.error(f"模型加载失败: {str(e)}")

    # 参数设置
    st.subheader("参数调整")
    threshold = st.slider("分割阈值", 0.0, 1.0, 0.5, 0.01)
    smoothness = st.slider("结果平滑度", 0, 10, 3)
    
    # 处理按钮
    if st.button("运行分割分析", use_container_width=True):
        if st.session_state.volume_data is None:
            st.warning("请先上传数据或使用示例数据！")
        elif st.session_state.model is None:
            st.warning("请先加载模型！")
        else:
            with st.spinner("正在处理影像数据..."):
                try:
                    # 初始化进度条
                    st.session_state.progress = 0.0
                    progress_bar = st.progress(0.0)
                    
                    # === 统一预处理逻辑 ===
                    volume_data = st.session_state.volume_data
                    
                    # 1. 阈值截取和归一化
                    preprocessed_volume = preprocess_volume(volume_data)
                    
                    # 2. 空间裁剪 (与preprocess.py一致)
                    if preprocessed_volume.shape[1] >= 480 and preprocessed_volume.shape[2] >= 480:
                        preprocessed_volume = crop_center(preprocessed_volume, 448, 448)
                    else:
                        st.warning("CT影像尺寸小于480x480，跳过空间裁剪")
                    # ============================================
                    
                    segmentation = predict_volume(
                        st.session_state.model, 
                        preprocessed_volume,
                        threshold=threshold,  # 传递阈值参数
                        smoothness=smoothness,  # 传递平滑度参数
                        progress_bar=progress_bar  # 进度条参数
                    )
                    
                    # 选择更具代表性的切片（中间位置±1）
                    depth = preprocessed_volume.shape[2]
                    mid_slice = depth // 2
                    slice_indices = [mid_slice - 1, mid_slice, mid_slice + 1]
                    
                    # 确保切片索引有效
                    slice_indices = [max(0, min(idx, depth-1)) for idx in slice_indices]
                    
                    # 使用更具代表性的切片
                    sample_images = np.stack([
                        preprocessed_volume[:, :, idx] for idx in slice_indices
                    ], axis=0)  # (3, H, W)
                    
                    sample_preds = np.stack([
                        segmentation[:, :, idx] for idx in slice_indices
                    ], axis=0)  # (3, H, W)
                    
                    # 可视化样本预测
                    debug_fig = visualize_sample_predictions(
                        sample_images, 
                        sample_preds
                    )
                    st.session_state.debug_fig = debug_fig
                    
                    # 更新进度条
                    progress_bar.progress(1.0)
                    
                    # 加载真实标签
                    if 'uploaded_label' in st.session_state:
                        try:
                            label_volume = load_label_file(st.session_state.uploaded_label)
                            # 调整标签尺寸匹配预处理
                            if label_volume.shape[1] >= 480 and label_volume.shape[2] >= 480:
                                label_volume = crop_center(label_volume, 448, 448)
                            st.session_state.label_volume = label_volume
                        except Exception as e:
                            st.error(f"真实标签加载失败: {str(e)}")
                    
                    # 评估分割结果（如果有真实标签）
                    if st.session_state.label_volume is not None:
                        # === 添加标签有效性检查 ===
                        if st.session_state.label_volume.shape == segmentation.shape:
                            with st.spinner("正在评估分割结果..."):
                                try:
                                    eval_results = evaluate_segmentation(
                                        segmentation,  # 使用当前分割结果
                                        st.session_state.label_volume
                                    )
                                    st.session_state.eval_results = eval_results
                                    if eval_results:
                                        st.success("评估完成!")
                                except Exception as e:
                                    st.error(f"评估失败: {str(e)}")
                        else:
                            st.warning("真实标签尺寸与预测结果不匹配，跳过评估")
                    else:
                        st.info("未提供真实标签，跳过评估")

                    # ===评估指标计算功能 ===
                    # eval_results = None  # 始终设置为None -> 删除这行
                    # st.session_state.eval_results = None -> 删除这行

                    # 存储结果
                    st.session_state.segmentation = segmentation
                    st.session_state.report_generated = False
                    
                    st.success("分割完成!")
                except Exception as e:
                    st.error(f"处理失败: {str(e)}")
                finally:
                    # 清理进度状态
                    if 'progress' in st.session_state:
                        del st.session_state.progress

    st.divider()
    
    # 报告生成
    st.header("📝 报告生成")
    
    # API密钥输入
    st.subheader("AI报告配置")
    secret_id = st.text_input("SecretId", type="password", help="腾讯云API密钥ID")
    secret_key = st.text_input("SecretKey", type="password", help="腾讯云API密钥")
    
    # 生成诊断报告按钮逻辑
    if st.button("生成诊断报告", use_container_width=True):
        if st.session_state.segmentation is None:
            st.warning("请先运行分割分析！")
        elif not secret_id or not secret_key:
            st.warning("请输入API密钥")
        else:
            with st.spinner("AI正在生成诊断报告..."):
                try:
                    # 先获取关键指标
                    liver_volume, num_tumors, tumor_volume, max_diameter = generate_diagnostic_report(
                        st.session_state.volume_data, 
                        st.session_state.segmentation,
                        st.session_state.eval_results
                    )
                    
                    # 确保所有关键指标都存储到session_state
                    st.session_state.liver_volume = liver_volume
                    st.session_state.num_tumors = num_tumors
                    st.session_state.tumor_volume = tumor_volume
                    st.session_state.max_diameter = max_diameter
                    
                    # 构造提示词
                    prompt = f"""
                    你是一名经验丰富的放射科医生。请根据以下肝脏CT分割结果生成专业诊断报告：
                    - 肝脏体积: {liver_volume:.2f} cm³
                    - 肿瘤数量: {num_tumors}
                    - 肿瘤总体积: {tumor_volume:.2f} cm³
                    - 最大肿瘤直径: {max_diameter:.2f} cm
                    报告需包含[检查技术]、[影像所见]和[诊断意见]三部分，重点描述肿瘤位置和特征。
                    """
                    
                    # 调用API生成报告
                    report = call_hunyuan_api(secret_id, secret_key, prompt)
                    st.session_state.report = report
                    st.session_state.report_generated = True
                    st.success("报告生成完成！")
                except Exception as e:
                    st.error(f"生成报告失败: {str(e)}")

# 主界面布局
tab1, tab2, tab3, tab4 = st.tabs(["📊 数据展示", "🔍 分割结果", "🌐 3D可视化", "📝 AI诊断报告"])

# 数据展示标签页 - 三视图切片展示
with tab1:
    if st.session_state.volume_data is not None and 'precomputed_slices' in st.session_state:
        # 删除两列布局，直接显示三视图切片浏览
        st.subheader("三视图切片浏览")
        
        # 创建三个切片选择器
        col_x, col_y, col_z = st.columns(3)
        
        with col_x:
            x_idx = st.slider(
                "X轴切片", 
                0, 
                st.session_state.volume_data.shape[2]-1, 
                st.session_state.current_x
            )
            st.session_state.current_x = x_idx

            # 获取预处理的冠状面图像
            image_data = st.session_state.precomputed_slices['coronal'][x_idx]
            image_data = np.rot90(image_data, k=2)
            image_data = np.flip(image_data, 1)
            # 显示图像
            fig_x, ax_x = plt.subplots(figsize=(5, 5), dpi=150)
            ax_x.imshow(image_data, cmap="gray", origin='lower')
            ax_x.set_aspect('auto')
            ax_x.set_adjustable('box')
            ax_x.axis('off')
            ax_x.set_title(f"冠状面 (X={x_idx})")
            st.pyplot(fig_x)
        
        with col_y:
            y_idx = st.slider(
                "Y轴切片", 
                0, 
                st.session_state.volume_data.shape[1]-1, 
                st.session_state.current_y
            )
            st.session_state.current_y = y_idx

            # 获取预处理的矢状面图像
            image_data = st.session_state.precomputed_slices['sagittal'][y_idx]
            image_data = np.rot90(image_data, k=2)
            image_data = np.flip(image_data, 1)
            
            fig_y, ax_y = plt.subplots(figsize=(5, 5), dpi=150)
            ax_y.imshow(image_data, cmap="gray", origin='lower')
            ax_y.set_aspect('auto')
            ax_y.set_adjustable('box')
            ax_y.axis('off')
            ax_y.set_title(f"矢状面 (Y={y_idx})")
            st.pyplot(fig_y)

        with col_z:
            z_idx = st.slider(
                "Z轴切片", 
                0, 
                st.session_state.volume_data.shape[0]-1, 
                st.session_state.current_z
            )
            st.session_state.current_z = z_idx

            # 获取预处理的横断面图像
            image_data = st.session_state.precomputed_slices['axial'][z_idx]
            image_data = np.rot90(image_data, k=2)
            image_data = np.flip(image_data, 1)
            
            fig_z, ax_z = plt.subplots(figsize=(5, 5), dpi=150)
            ax_z.imshow(image_data, cmap="gray", origin='lower')
            ax_z.set_aspect('auto')
            ax_z.set_adjustable('box')
            ax_z.axis('off')
            ax_z.set_title(f"横断面 (Z={z_idx})")
            st.pyplot(fig_z)
        
    else:
        st.info("请上传CT影像开始分析")

# 分割结果标签页
with tab2:
    if st.session_state.segmentation is not None:
        # === None检查和处理 ===
        if st.session_state.get('eval_results') is not None and isinstance(st.session_state.eval_results, dict):
            st.subheader("模型评估结果")
            eval_results = st.session_state.eval_results
            
            # 展示关键指标
            col1, col2, col3, col4 = st.columns(4)
            col1.metric("肝脏Dice系数", f"{eval_results['avg_liver_dice']:.4f}")
            col2.metric("肿瘤Dice系数", f"{eval_results['avg_tumor_dice']:.4f}")
            col3.metric("肝脏IoU", f"{eval_results['avg_liver_iou']:.4f}")
            col4.metric("肿瘤IoU", f"{eval_results['avg_tumor_iou']:.4f}")
            
            st.divider()
        # === 添加评估失败情况处理 ===
        elif st.session_state.get('eval_results') is None:
            st.warning("评估未执行或失败")
        
        st.subheader("分割结果可视化")
        
        # 切片选择器
        col_x, col_y, col_z = st.columns(3)
        
        with col_x:
            seg_x_idx = st.slider(
                "X轴切片 (冠状面)", 
                0, 
                st.session_state.volume_data.shape[2]-1,
                st.session_state.current_x,
                key="seg_x_slider"
            )
        
        with col_y:
            seg_y_idx = st.slider(
                "Y轴切片 (矢状面)", 
                0, 
                st.session_state.volume_data.shape[1]-1,
                st.session_state.current_y,
                key="seg_y_slider"
            )
        
        with col_z:
            seg_z_idx = st.slider(
                "Z轴切片 (横断面)", 
                0, 
                st.session_state.volume_data.shape[0]-1,
                st.session_state.current_z,
                key="seg_z_slider"
            )
        
        # 创建三列布局
        col1, col2, col3 = st.columns(3)
        
        # 冠状面视图 - 调整为垂直布局
        with col1:
            st.markdown("**冠状面**")
            
            # 2. 分割结果
            st.markdown("**分割结果**")


            fig2, ax2 = plt.subplots(figsize=(5,5), dpi=150)
            seg_slice_coronal = st.session_state.segmentation[:, :, seg_x_idx]

            # 1. 逆时针旋转90度
            rotated_seg = np.rot90(seg_slice_coronal, k=-1)  # 默认是逆时针90度
            # 2. 水平翻转
            flipped_seg = np.fliplr(rotated_seg)  # 左右翻转

            # 显示处理后的图像
            ax2.imshow(
                flipped_seg,  # 使用旋转并翻转后的数据
                cmap='jet', 
                vmin=0, 
                vmax=2, 
                origin='lower'
            )
            ax2.set_aspect('auto')
            ax2.set_adjustable('box')
            ax2.axis('off')
            st.pyplot(fig2)
            
            # 3. 真实标签结果（如果存在）
            if st.session_state.label_volume is not None:
                st.markdown("**真实标签**")
                fig3, ax3 = plt.subplots(figsize=(5,5), dpi=150)      
                
                # 获取标签数据并旋转90度
                label_slice = st.session_state.label_volume[:, :, seg_x_idx]
                rotated_label = np.rot90(label_slice)  # 逆时针旋转90度
                
                ax3.imshow(
                    rotated_label,  # 使用旋转后的数据
                    cmap='jet', 
                    vmin=0, 
                    vmax=2
                )
                ax3.set_aspect('auto')
                ax3.set_adjustable('box')
                ax3.axis('off')
                st.pyplot(fig3)

            # 获取原始CT图像
            ct_image = st.session_state.precomputed_slices['coronal'][seg_x_idx]

            # 逆时针旋转CT图像
            
            # 4. 预测结果叠加显示
            st.markdown("**预测结果叠加**")
            fig_pred, ax_pred = plt.subplots(figsize=(5,5), dpi=150)

            # 使用相同的CT背景
            ax_pred.imshow(ct_image, cmap="gray", origin='lower')

            # 显示预测结果（橙色/红色）
            pred_liver = np.ma.masked_where(seg_slice_coronal != 1, seg_slice_coronal)
            pred_tumor = np.ma.masked_where(seg_slice_coronal != 2, seg_slice_coronal)

            ax_pred.imshow(np.rot90(pred_liver), cmap="autumn", alpha=0.7)
            ax_pred.imshow(np.rot90(pred_tumor), cmap="Dark2", alpha=0.7)

            ax_pred.set_aspect('auto')
            ax_pred.set_adjustable('box')
            ax_pred.axis('off')
            st.pyplot(fig_pred)
            
            # 4. 真实标签叠加显示
            st.markdown("**真实标签叠加**")
            fig_true, ax_true = plt.subplots(figsize=(5,5), dpi=150)
            ax_true.imshow(ct_image, cmap="gray", origin='lower')

            # 显示真实标签（绿色/蓝色）
            if st.session_state.label_volume is not None:
                label_slice = st.session_state.label_volume[:, :, seg_x_idx]
                true_liver = np.ma.masked_where(label_slice != 1, label_slice)
                true_tumor = np.ma.masked_where(label_slice != 2, label_slice)

                ax_true.imshow(np.rot90(true_liver), cmap="autumn", alpha=0.7)
                ax_true.imshow(np.rot90(true_tumor), cmap="Dark2", alpha=0.7)

            ax_true.set_aspect('auto')
            ax_true.set_adjustable('box')
            ax_true.axis('off')
            st.pyplot(fig_true)
        
        # 矢状面视图
        with col2:
            st.markdown("**矢状面**")
            
            # 2. 分割结果
            st.markdown("**分割结果**")
            fig2, ax2 = plt.subplots(figsize=(5,5), dpi=150)
            seg_slice_sagittal = st.session_state.segmentation[:, seg_y_idx, :]

            # 逆时针旋转90度 + 水平翻转
            rotated_seg = np.rot90(seg_slice_sagittal, k=-1)  # k=1 表示逆时针旋转90度
            flipped_seg = np.fliplr(rotated_seg)  # 水平翻转

            ax2.imshow(
                flipped_seg,  # 使用旋转并翻转后的数据
                cmap='jet', 
                vmin=0, 
                vmax=2, 
                origin='lower'
            )
            ax2.set_aspect('auto')
            ax2.set_adjustable('box')
            ax2.axis('off')
            st.pyplot(fig2)

            # 3. 真实标签结果（如果存在）
            if st.session_state.label_volume is not None:
                st.markdown("**真实标签**")
                fig3, ax3 = plt.subplots(figsize=(5,5), dpi=150)
                
                # 逆时针旋转90度 + 水平翻转
                label_slice = st.session_state.label_volume[:, seg_y_idx, :]
                rotated_label = np.rot90(label_slice, k=-1)  # k=1 表示逆时针旋转90度
                flipped_label = np.fliplr(rotated_label)  # 水平翻转
                
                ax3.imshow(
                    flipped_label,  # 使用旋转并翻转后的数据
                    cmap='jet', 
                    vmin=0, 
                    vmax=2,
                    origin='lower',
                    aspect='auto'
                )
                
                ax3.set_aspect('auto')
                ax3.set_adjustable('box')
                ax3.axis('off')
                st.pyplot(fig3)

            # 4. 预测结果叠加显示
            st.markdown("**预测结果叠加**")
            fig_pred, ax_pred = plt.subplots(figsize=(5,5), dpi=150)

            # 原始CT背景 - 逆时针旋转90度
            ct_slice = st.session_state.precomputed_slices['sagittal'][seg_y_idx]
            ax_pred.imshow(
                ct_slice,
                cmap="gray",
                origin='lower'
            )

            # 预测结果（轮廓）并逆时针旋转90度
            pred_liver = np.ma.masked_where(seg_slice_sagittal != 1, seg_slice_sagittal == 1)
            pred_tumor = np.ma.masked_where(seg_slice_sagittal != 2, seg_slice_sagittal == 2)
            ax_pred.imshow(np.rot90(pred_liver, k=1), cmap="autumn", alpha=0.7)
            ax_pred.imshow(np.rot90(pred_tumor, k=1), cmap="Dark2", alpha=0.7)

            ax_pred.set_aspect('auto')
            ax_pred.set_adjustable('box')
            ax_pred.axis('off')
            st.pyplot(fig_pred)
            
            # 4. 真实标签叠加显示
            st.markdown("**真实标签叠加**")
            fig_true, ax_true = plt.subplots(figsize=(5,5), dpi=150)

            # 原始CT背景 - 逆时针旋转90度
            ct_slice = st.session_state.precomputed_slices['sagittal'][seg_y_idx]
            ax_true.imshow(
                ct_slice,
                cmap="gray",
                origin='lower'
            )

            # 真实标签（绿色，半透明）并逆时针旋转90度
            if st.session_state.label_volume is not None:
                true_liver = np.ma.masked_where(
                    st.session_state.label_volume[:, seg_y_idx, :] != 1,
                    st.session_state.label_volume[:, seg_y_idx, :] == 1
                )
                true_tumor = np.ma.masked_where(
                    st.session_state.label_volume[:, seg_y_idx, :] != 2,
                    st.session_state.label_volume[:, seg_y_idx, :] == 2
                )
                ax_true.imshow(np.rot90(true_liver, k=1), cmap="autumn", alpha=0.7)
                ax_true.imshow(np.rot90(true_tumor, k=1), cmap="Dark2", alpha=0.7)

            ax_true.set_aspect('auto')
            ax_true.set_adjustable('box')
            ax_true.axis('off')
            st.pyplot(fig_true)

        # 横断面视图 - 显示参数和切片索引
        with col3:
            st.markdown("**横断面**")
            
            # 2. 分割结果
            st.markdown("**分割结果**")
            fig2, ax2 = plt.subplots(figsize=(5,5), dpi=150)
            seg_slice_axial = st.session_state.segmentation[seg_z_idx, :, :]

            # 逆时针旋转90度 + 水平翻转
            rotated_seg = np.rot90(seg_slice_axial, k=-1)  # k=1 表示逆时针旋转90度
            flipped_seg = np.fliplr(rotated_seg)  # 水平翻转

            ax2.imshow(
                flipped_seg,  # 使用旋转并翻转后的数据
                cmap='jet', 
                vmin=0, 
                vmax=2, 
                origin='lower'
            )
            ax2.set_aspect('auto')
            ax2.set_adjustable('box')
            ax2.axis('off')
            st.pyplot(fig2)

            # 3. 真实标签结果（如果存在）
            if st.session_state.label_volume is not None:
                st.markdown("**真实标签**")
                fig3, ax3 = plt.subplots(figsize=(5,5), dpi=150)
                
                # 逆时针旋转90度 + 水平翻转
                label_slice = st.session_state.label_volume[seg_z_idx, :, :]
                rotated_label = np.rot90(label_slice, k=-1)  # k=1 表示逆时针旋转90度
                flipped_label = np.fliplr(rotated_label)  # 水平翻转
                
                ax3.imshow(
                    flipped_label,  # 使用旋转并翻转后的数据
                    cmap='jet', 
                    vmin=0, 
                    vmax=2,
                    origin='lower',
                    aspect='auto'
                )
                
                ax3.set_aspect('auto')
                ax3.set_adjustable('box')
                ax3.axis('off')
                st.pyplot(fig3)

            # 4. 预测结果叠加显示
            st.markdown("**预测结果叠加**")
            fig_pred, ax_pred = plt.subplots(figsize=(5,5), dpi=150)

            # 原始CT背景 - 逆时针旋转90度
            rotated_ct = st.session_state.precomputed_slices['axial'][seg_z_idx]
            ax_pred.imshow(
                rotated_ct,  # 使用旋转后的CT图像
                cmap="gray",
                origin='lower'
            )

            # 预测结果（轮廓）- 同样需要旋转
            pred_liver = np.ma.masked_where(seg_slice_axial != 1, seg_slice_axial == 1)
            pred_tumor = np.ma.masked_where(seg_slice_axial != 2, seg_slice_axial == 2)
            ax_pred.imshow(np.rot90(pred_liver, k=1), cmap="autumn", alpha=0.7)  # 旋转肝脏预测
            ax_pred.imshow(np.rot90(pred_tumor, k=1), cmap="Dark2", alpha=0.7)    # 旋转肿瘤预测

            ax_pred.set_aspect('auto')
            ax_pred.set_adjustable('box')
            ax_pred.axis('off')
            st.pyplot(fig_pred)

            # 4. 真实标签叠加显示
            st.markdown("**真实标签叠加**")
            fig_true, ax_true = plt.subplots(figsize=(5,5), dpi=150)

            # 原始CT背景 - 逆时针旋转90度
            rotated_ct = st.session_state.precomputed_slices['axial'][seg_z_idx]
            ax_true.imshow(
                rotated_ct,  # 使用旋转后的CT图像
                cmap="gray",
                origin='lower'
            )

            # 真实标签（绿色，半透明）- 同样需要旋转
            if st.session_state.label_volume is not None:
                true_liver = np.ma.masked_where(
                    st.session_state.label_volume[seg_z_idx, :, :] != 1, 
                    st.session_state.label_volume[seg_z_idx, :, :] == 1
                )
                true_tumor = np.ma.masked_where(
                    st.session_state.label_volume[seg_z_idx, :, :] != 2, 
                    st.session_state.label_volume[seg_z_idx, :, :] == 2
                )
                ax_true.imshow(np.rot90(true_liver, k=1), cmap="autumn", alpha=0.7)  # 旋转肝脏标签
                ax_true.imshow(np.rot90(true_tumor, k=1), cmap="Dark2", alpha=0.7)   # 旋转肿瘤标签

            ax_true.set_aspect('auto')
            ax_true.set_adjustable('box')
            ax_true.axis('off')
            st.pyplot(fig_true)
        
        st.divider()
        
    else:
        st.info("请先运行分割分析查看结果")

# 3D可视化标签页
with tab3:
    # === 将渲染模式提示框移到条件判断外部 ===
    st.subheader("3D可视化")
    if st.session_state.segmentation is not None:
        # 创建两列布局
        col1, col2 = st.columns([2, 1])
        
        with col1:
            # === 彻底禁用硬件渲染 ===
            def create_visualization(segmentation, show_liver, show_tumor, opacity, _camera_position=None, view=None):
                try:
                    # 移除backend参数，仅依赖环境变量设置
                    plotter = pv.Plotter(
                        window_size=[600, 400],
                        off_screen=True
                    )
                    
                    grid = pv.ImageData()
                    grid.dimensions = np.array(segmentation.shape) + 1
                    grid.spacing = (1, 1, 1)
                    grid.origin = (0, 0, 0)
                    grid.cell_data["values"] = segmentation.flatten(order="F")

                    if show_liver:
                        liver = grid.threshold(0.5, scalars="values")
                        if liver.n_points > 0:
                            liver_surface = liver.extract_geometry()
                            # 清除无效缓冲区属性
                            liver_surface.clear_data()
                            plotter.add_mesh(liver_surface, 
                                       color="#ff9a3c", 
                                       opacity=opacity, 
                                       name="Liver",
                                       smooth_shading=True)

                    if show_tumor:
                        tumor = grid.threshold(1.5, scalars="values")
                        if tumor.n_points > 0:
                            tumor_surface = tumor.extract_geometry()
                            # 清除无效缓冲区属性
                            tumor_surface.clear_data()
                            plotter.add_mesh(tumor_surface, 
                                        color="#ff0000",
                                        opacity=0.9, 
                                        name="Tumor",
                                        smooth_shading=True)

                    if not show_liver and not show_tumor:
                        plotter.add_text("请至少选择一项以显示模型", position="upper_edge", font_size=18, color="white")

                    plotter.set_background("#2c3e50")
                    
                    # 根据视图选择调整相机位置 - 使用PyVista内置视图方法
                    if _camera_position:
                        plotter.camera_position = _camera_position
                    elif view == "冠状面":
                        plotter.view_xz()  # 使用内置方法设置冠状面视图
                    elif view == "矢状面":
                        plotter.view_yz()  # 使用内置方法设置矢状面视图
                    elif view == "横断面":
                        plotter.view_xy()  # 使用内置方法设置横断面视图
                    else:  # 3D视图
                        plotter.view_isometric()  # 使用内置方法设置等轴视图
                    
                    # 添加边界框
                    plotter.show_bounds(grid=False)
                    
                    return plotter
                except Exception as e:
                    # 简化错误处理，避免访问未初始化属性
                    st.error(f"创建3D可视化失败: {str(e)}")
                    return None
            
            # === 缓存键包含所有渲染参数 ===
            render_key = f"pv_plotter_{st.session_state.model_type}_{st.session_state.show_liver}_{st.session_state.show_tumor}_{st.session_state.opacity}_{st.session_state.view}"
            
            # 根据模型类型选择数据源
            if st.session_state.model_type == '预测模型':
                seg_data = st.session_state.segmentation
            else:
                if st.session_state.label_volume is None:
                    st.error("请先上传真实标签数据！")
                    st.stop()  # 替换 return 为 st.stop()
                seg_data = st.session_state.label_volume
            
            try:
                # 渲染 3D 组件
                plotter = create_visualization(
                    seg_data,
                    st.session_state.show_liver,
                    st.session_state.show_tumor,
                    st.session_state.opacity,
                    st.session_state.get('camera_position'),
                    st.session_state.view
                )
                
                if plotter is not None:
                    # 存储当前plotter对象到session_state
                    st.session_state.current_plotter = plotter
                    
                    stpyvista(plotter, key=render_key)
                        
                    # 存储当前相机位置到session_state
                    st.session_state.camera_position = plotter.camera_position
                else:
                    st.warning("3D渲染引擎初始化失败，已回退到软件渲染模式")
            except Exception as e:
                st.error("3D可视化渲染失败，请重试")
                st.error(f"错误详情: {str(e)}")
                import traceback
                st.code(traceback.format_exc(), language='python')
        
        with col2:
            # === 使用表单封装控制选项 ===
            with st.form(key="3d_controls_form"):
                # === 模型选择单选按钮 ===
                st.markdown("**模型选择**")
                # === 绑定到临时状态变量 ===
                st.radio(
                    "选择显示的模型",
                    ("预测模型", "真实模型"),
                    index=0 if st.session_state.temp_model_type == '预测模型' else 1,
                    key='temp_model_type',
                    label_visibility="collapsed"
                )
                
                # === 相机控制按钮组 ===
                st.markdown("**相机控制**")
                col_btn1, col_btn2, col_btn3 = st.columns(3)
                with col_btn1:
                    if st.form_submit_button("冠状面", use_container_width=True):
                        st.session_state.temp_view = "冠状面"
                with col_btn2:
                    if st.form_submit_button("矢状面", use_container_width=True):
                        st.session_state.temp_view = "矢状面"
                with col_btn3:
                    if st.form_submit_button("横断面", use_container_width=True):
                        st.session_state.temp_view = "横断面"
                
                st.divider()
                
                # === 移除原有更新按钮 ===
                
                # 可视化选项
                st.markdown("**显示选项**")
                # === 绑定到临时状态变量 ===
                st.session_state.temp_show_liver = st.checkbox(
                    "显示肝脏", 
                    value=st.session_state.temp_show_liver, 
                    key='temp_show_liver_checkbox'
                )

                st.session_state.temp_show_tumor = st.checkbox(
                    "显示肿瘤", 
                    value=st.session_state.temp_show_tumor, 
                    key='temp_show_tumor_checkbox'
                )

                st.session_state.temp_opacity = st.slider(
                    "肝脏透明度", 
                    0.0, 1.0, 
                    value=st.session_state.temp_opacity, 
                    step=0.1, 
                    key='temp_opacity_slider'
                )
                
                # === 应用更改按钮 ===
                apply_changes = st.form_submit_button("应用更改", use_container_width=True)
                if apply_changes:
                    # 同步临时状态到实际渲染状态
                    st.session_state.model_type = st.session_state.temp_model_type
                    st.session_state.show_liver = st.session_state.temp_show_liver
                    st.session_state.show_tumor = st.session_state.temp_show_tumor
                    st.session_state.opacity = st.session_state.temp_opacity
                    st.session_state.view = st.session_state.temp_view
                    st.success("已应用所有更改!")
            
            st.divider()
            
            st.markdown("**导出选项**")
            if st.button("保存3D模型", use_container_width=True):
                # 根据模型类型选择数据源
                if st.session_state.model_type == '预测模型':
                    seg_data = st.session_state.segmentation
                else:
                    if st.session_state.label_volume is None:
                        st.error("请先上传真实标签数据！")
                        st.stop()  # 替换 return 为 st.stop()
                    seg_data = st.session_state.label_volume
                
                os.makedirs('exports', exist_ok=True)
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                filename = f"exports/liver_model_{timestamp}.stl"
                
                # 使用PyVista保存STL文件的逻辑
                try:
                    # 创建网格数据
                    grid = pv.ImageData()
                    grid.dimensions = np.array(seg_data.shape) + 1
                    grid.spacing = (1, 1, 1)
                    grid.origin = (0, 0, 0)
                    grid.cell_data["values"] = seg_data.flatten(order="F")

                    # 提取肝脏和肿瘤几何体
                    all_meshes = []
                    
                    if st.session_state.show_liver:
                        liver = grid.threshold(0.5, scalars="values")
                        if liver.n_points > 0:
                            liver_mesh = liver.extract_geometry()
                            liver_mesh.cell_data["label"] = np.ones(liver_mesh.n_cells, dtype=int) * 1
                            all_meshes.append(liver_mesh)
                            
                    if st.session_state.show_tumor:
                        tumor = grid.threshold(1.5, scalars="values")
                        if tumor.n_points > 0:
                            tumor_mesh = tumor.extract_geometry()
                            tumor_mesh.cell_data["label"] = np.ones(tumor_mesh.n_cells, dtype=int) * 2
                            all_meshes.append(tumor_mesh)

                    # 合并所有网格
                    combined_mesh = all_meshes[0]
                    for mesh in all_meshes[1:]:
                        combined_mesh = combined_mesh + mesh

                    # 保存为STL文件
                    combined_mesh.save(filename)
                    st.success(f"3D模型已成功保存至: {filename}")
                    
                except Exception as e:
                    st.error(f"保存3D模型失败: {str(e)}")
            if st.button("截图", use_container_width=True):
                # 确保当前存在有效的plotter对象
                if 'current_plotter' not in st.session_state or st.session_state.current_plotter is None:
                    st.error("请先渲染3D模型再进行截图")
                    st.stop()
                
                os.makedirs('exports', exist_ok=True)
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                filename = f"exports/screenshot_{timestamp}.png"
                
                try:
                    # 直接使用当前已渲染的plotter对象进行截图
                    plotter = st.session_state.current_plotter
                    
                    # 确保渲染器有效
                    if plotter is None:
                        st.error("3D渲染引擎未初始化，无法截图")
                        st.stop()
                    
                    # 应用当前相机位置
                    if 'camera_position' in st.session_state:
                        plotter.camera_position = st.session_state.camera_position
                    
                    # 执行截图 (使用当前相机视角)
                    plotter.screenshot(filename)
                    st.success(f"截图已保存为: {filename}")
                except Exception as e:
                    st.error("截图失败，请检查渲染器状态")
                    st.error(f"错误详情: {str(e)}")
                    import traceback
                    st.code(traceback.format_exc(), language='python')

    else:
        st.info("请先运行分割分析查看3D可视化")

# AI诊断报告标签页 - 集成第二意见功能
with tab4:
    if st.session_state.report_generated:
        # 显示诊断报告
        with st.container():
            st.markdown(st.session_state.report, unsafe_allow_html=True)
            
            st.divider()
            
            # 报告操作按钮
            col1, col2, col3 = st.columns(3)
            with col1:
                if st.button("导出为PDF", use_container_width=True):
                    if 'report' in st.session_state:
                        # 生成PDF文件
                        pdf_file = generate_pdf_report(st.session_state.report)
                        
                        if pdf_file:
                            # 提供下载链接
                            with open(pdf_file, "rb") as f:
                                st.download_button(
                                    label="点击下载PDF报告",
                                    data=f,
                                    file_name=f"肝脏诊断报告_{datetime.now().strftime('%Y%m%d')}.pdf",
                                    mime="application/pdf"
                                )

            with col2:
                if st.button("生成第二意见", use_container_width=True):
                    # 检查关键指标是否存在
                    required_keys = ['liver_volume', 'num_tumors', 'tumor_volume', 'max_diameter']
                    if all(key in st.session_state for key in required_keys):
                        liver_volume = st.session_state.liver_volume
                        num_tumors = st.session_state.num_tumors
                        tumor_volume = st.session_state.tumor_volume
                        max_diameter = st.session_state.max_diameter
                        
                        with st.spinner("生成第二意见中..."):
                            # 构造包含3D可视化信息的提示词
                            second_prompt = f"""
                            基于以下肝脏CT分析结果提供第二诊断意见：
                            {st.session_state.report}
                            
                            补充3D可视化信息：
                            - 肝脏形态: {'饱满' if liver_volume > 1500 else '正常'}
                            - 肿瘤数量: {num_tumors}
                            - 肿瘤总体积: {tumor_volume:.2f} cm³
                            - 最大肿瘤直径: {max_diameter:.2f} cm
                            
                            请从不同角度分析肿瘤性质和治疗建议。
                            """
                            
                            # 调用API生成第二意见
                            second_opinion = call_hunyuan_api(secret_id, secret_key, second_prompt)
                            st.session_state.second_opinion = second_opinion
                            st.session_state.show_second_opinion = True
                            st.success("第二意见生成完成！")
                    else:
                        st.warning("请先生成诊断报告！缺少关键指标")

            with col3:
                if st.button("导出为EMR", use_container_width=True):
                    if 'report' in st.session_state:
                        try:
                            # 创建EMR格式数据
                            emr_data = {
                                "patient_info": {
                                    "study_date": datetime.now().strftime("%Y-%m-%d"),
                                    "report_type": "肝脏影像分析报告"
                                },
                                "findings": {
                                    "liver_volume": st.session_state.get('liver_volume', 0),
                                    "tumor_count": st.session_state.get('num_tumors', 0),
                                    "tumor_volume": st.session_state.get('tumor_volume', 0),
                                    "max_diameter": st.session_state.get('max_diameter', 0)
                                },
                                "report_content": st.session_state.report,
                                "analysis_date": datetime.now().isoformat()
                            }
                            
                            # 转换为JSON格式
                            emr_json = json.dumps(emr_data, ensure_ascii=False, indent=2)
                            
                            # 提供下载
                            st.download_button(
                                label="点击下载EMR文件",
                                data=emr_json,
                                file_name=f"肝脏EMR报告_{datetime.now().strftime('%Y%m%d')}.json",
                                mime="application/json"
                            )
                        except Exception as e:
                            st.error(f"导出EMR失败: {str(e)}")
                    else:
                        st.warning("请先生成诊断报告！")

            # === 第二意见展示区域 ===
            if st.session_state.get('show_second_opinion', False):
                st.divider()
                st.subheader("第二意见")
                st.markdown(st.session_state.second_opinion, unsafe_allow_html=True)

# 页脚信息
st.divider()
st.markdown("""
<div style="text-align:center;color:#7f8c8d;font-size:0.9em;margin-top:30px;">
    <p>课程设计项目 © 2025</p>
    <p>项目成员：xzj、lx</p>
    <p>本系统为课程设计演示项目，仅供学习参考，不能直接用于临床诊断</p>
</div>
""", unsafe_allow_html=True)